package toolx

import (
	"fmt"
	"os"
	"os/exec"
	"os/signal"
	"regexp"
	"sort"
	"syscall"
	"time"

	"github.com/howeyc/fsnotify"
	"github.com/mitchellh/go-ps"
)

var (
	cmd          *exec.Cmd
	listenedDirs = make(map[string]any)

	// 不需要监听的目录和文件
	ignoreDirsReg = []string{
		`(.+)\` + PS + `logs$`,  // 日志目录
		`(.+)\` + PS + `\.(.+)`, // 隐藏目录
	}
	watchFilesReg = []string{
		`(.+).go$`,
		`(.+).yaml$`,
		`(.+).html$`,
	}
)

func Run() {
	// 添加监听
	watcher, err := fsnotify.NewWatcher()
	if err != nil {
		panic(err)
	}

	// 开启协程监听变化
	go func() {
		isReruning := false
		for {
			select {
			case e := <-watcher.Event:
				if cmd == nil || cmd.Process == nil || cmd.Process.Pid <= 0 {
					continue
				}
				if isReruning {
					continue
				}
				if isHiddenFile(e.Name) {
					continue
				}
				if !isWatchFile(e.Name) {
					continue
				}
				if e.IsCreate() || e.IsDelete() || e.IsModify() || e.IsRename() {
					isReruning = true
					go func() {
						kill(cmd.Process.Pid)
						time.Sleep(2 * time.Second) // 等待2s
						fmt.Println()               // 空一行
						addWatchDirs(watcher, ".")
						isReruning = false
						run()
					}()
				}
			case err := <-watcher.Error:
				panic(err)
			}
		}
	}()

	addWatchDirs(watcher, ".") // 添加监听目录
	fmt.Println()              // 添加一个空行
	run()                      // 运行

	// 阻塞进程 接收到
	exit := make(chan os.Signal, 1)
	signal.Notify(exit, syscall.SIGINT, syscall.SIGTERM)
	<-exit
}

// 运行
func run() {
	cmd = exec.Command("go", "run", ".")
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr

	// 整理代码
	prevCmd := exec.Command("go", "mod", "tidy")
	prevCmd.Stdout = os.Stdout
	prevCmd.Stderr = os.Stderr
	if err := prevCmd.Run(); err != nil {
		panic(err)
	}

	go func() {
		_ = cmd.Run()
	}()
}

// 杀死进程
func kill(pid int) {
	pids := allProcesses(pid)
	for _, id := range pids {
		ps, err := os.FindProcess(id)
		if err != nil {
			fmt.Println(err)
			continue
		}
		err = ps.Kill()
		if err != nil {
			// 失败后2s后重试
			time.AfterFunc(2*time.Second, func() {
				_ = ps.Kill()
			})
		}
	}
	if err := cmd.Process.Kill(); err != nil {
		fmt.Println(err)
	}
}

// 根据pid查询所有子进程id
func allProcesses(pid int) []int {
	pss, err := ps.Processes()
	if err != nil {
		panic(err)
	}

	// 对进程进行排序
	sort.Slice(pss, func(i, j int) bool {
		ppidLess := pss[i].PPid() < pss[j].PPid()
		pidLess := pss[i].PPid() == pss[j].PPid() && pss[i].Pid() < pss[j].Pid()

		return ppidLess || pidLess
	})

	// 查找所有子进程
	pidPool := map[int]any{pid: nil}
	for _, p := range pss {
		ppid := p.PPid()
		if _, exists := pidPool[ppid]; exists {
			pidPool[p.Pid()] = nil
		}
	}

	// 构造返回
	rets := make([]int, 0)
	for id := range pidPool {
		rets = append(rets, id)
	}

	return rets
}

// 判断是否是被忽略的文件
func isWatchFile(fName string) bool {
	for _, ignoreFileReg := range watchFilesReg {
		if match, err := regexp.Match(ignoreFileReg, []byte(fName)); err == nil && match {
			return true
		}
	}
	return false
}

// 判断是否是隐藏文件
func isHiddenFile(fName string) bool {
	if match, err := regexp.Match(`^\.(.+)`, []byte(fName)); err == nil && match {
		return true
	}
	if match, err := regexp.Match(`(.+)~$`, []byte(fName)); err == nil && match {
		return true
	}
	return false
}

// 添加监听目录
func addWatchDirs(watcher *fsnotify.Watcher, pDir string) {
	// 如果是忽略的目录跳过
	for _, ignoreDirReg := range ignoreDirsReg {
		if match, err := regexp.Match(ignoreDirReg, []byte(pDir)); err == nil && match {
			return
		}
	}

	// 添加监听目录
	if _, has := listenedDirs[pDir]; !has {
		if err := watcher.Watch(pDir); err != nil {
			panic(err)
		}
		listenedDirs[pDir] = nil
		fmt.Printf("监听目录: %s\n", pDir)
	}

	// 递归添加子目录
	p, err := os.Open(pDir)
	if err != nil {
		panic(err)
	}
	fis, err := p.ReadDir(0)
	if err != nil {
		panic(err)
	}
	for _, fi := range fis {
		if fi.IsDir() {
			addWatchDirs(watcher, pDir+PS+fi.Name())
		}
	}
}
